package com.excilys.capicsoubank.services.impl;

import java.util.List;

import org.apache.commons.lang3.tuple.Pair;
import org.joda.time.DateTime;
import org.joda.time.DateTimeZone;
import org.joda.time.IllegalFieldValueException;
import org.joda.time.format.DateTimeFormat;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.Assert;

import com.excilys.capicsoubank.dao.AccountDao;
import com.excilys.capicsoubank.dao.TransactionDao;
import com.excilys.capicsoubank.entities.Account;
import com.excilys.capicsoubank.entities.Transaction;
import com.excilys.capicsoubank.entities.TransactionType;
import com.excilys.capicsoubank.exception.NegativeBalanceException;
import com.excilys.capicsoubank.services.AccountService;
import com.excilys.capicsoubank.services.TransactionService;
import com.excilys.capicsoubank.util.DateUtil;

/**
 * Implémentation du service de transaction.
 * 
 * @author Guillaume Lenoir
 * 
 */
@Service
@Transactional(readOnly = true)
public class TransactionServiceImpl implements TransactionService {

	@Autowired
	private AccountDao accountDao;

	@Autowired
	private TransactionDao transactionDao;

	@Autowired
	private AccountService accountService;

	private static final Logger LOGGER = LoggerFactory
			.getLogger(TransactionServiceImpl.class);

	@Override
	@Transactional(readOnly = false)
	public void transferBetweenAccounts(Integer accountIdSource,
			Integer accountIdDestination, long amount)
			throws NegativeBalanceException {

		Assert.notNull(accountIdSource);
		Assert.notNull(accountIdDestination);
		Assert.isTrue(amount > 0);

		Account accountSource = accountService.getAccountById(accountIdSource);
		Account accountDestination = accountService
				.getAccountById(accountIdDestination);

		if (accountSource.getBalance() - amount < 0)
			throw new NegativeBalanceException(accountSource,
					accountSource.getBalance() - amount);

		DateTime dateTime = DateTime.now(DateTimeZone.UTC);

		String dateLabel = " ("
				+ dateTime.withZone(DateTimeZone.getDefault()).toString(
						DateTimeFormat.fullTime()) + ")";

		Transaction transactionTransmitter = new Transaction.Builder()
				.account(accountSource)
				.amount(-amount)
				.date(dateTime)
				.label("Virement vers compte n°"
						+ accountDestination.getAccountId() + dateLabel)
				.transactionType(TransactionType.TRANSFER).build();
		Transaction transactionReceiver = new Transaction.Builder()
				.account(accountDestination)
				.amount(amount)
				.date(dateTime)
				.label("Virement du compte n°" + accountSource.getAccountId()
						+ dateLabel).transactionType(TransactionType.TRANSFER)
				.build();

		transactionDao.saveEntity(transactionTransmitter);
		transactionDao.saveEntity(transactionReceiver);

		accountSource.addAmountToBalance(-amount);
		accountDestination.addAmountToBalance(amount);

		accountDao.updateEntity(accountSource);
		accountDao.updateEntity(accountDestination);
	}

	@Override
	public long getSumCardTransactionsByYearByMonth(Integer accountId,
			int year, int month) {

		Assert.notNull(accountId);

		TransactionType[] types = { TransactionType.CARD };
		return transactionDao.getSumTransactionsByPeriodByType(
				accountService.getAccountById(accountId),
				DateUtil.firstDayOfMonth(year, month),
				DateUtil.lastDayOfMonth(year, month), types);
	}

	@Override
	public boolean isInHistory(int year, int month) {
		try {
			DateTime now = DateTime.now();
			DateTime startHistory = now.minusMonths(SIZE_HISTORY - 1)
					.withDayOfMonth(1).withHourOfDay(0).withMinuteOfHour(0)
					.withSecondOfMinute(0).withMillisOfSecond(0);
			DateTime dateToCheck = now.withYear(year).withMonthOfYear(month);

			Object[] params = { now, dateToCheck, startHistory };
			LOGGER.error("{} > {} > {}", params);

			return dateToCheck.compareTo(now) <= 0
					&& dateToCheck.compareTo(startHistory) >= 0;
		} catch (IllegalFieldValueException e) {
			return false;
		}
	}

	@Override
	public Pair<List<Transaction>, Long> getTransactionByAccountByYearByMonthByTypeWithPagination(
			Integer accountId, int year, int month, TransactionType[] types,
			int page, int sizePage) {

		Assert.notNull(accountId);
		Assert.notEmpty(types);
		Assert.isTrue(page >= 1);

		Account account = accountService.getAccountById(accountId);

		DateTime startDate = DateUtil.firstDayOfMonth(year, month);
		DateTime endDate = DateUtil.lastDayOfMonth(year, month);

		long transactionsCount = transactionDao
				.getCountTransactionByAccountByPeriodByType(account, startDate,
						endDate, types);

		Assert.isTrue(page <= 1 || transactionsCount > sizePage * (page - 1));

		List<Transaction> transactions = transactionDao
				.getTransactionByAccountByPeriodByTypeWithPagination(account,
						startDate, endDate, types, sizePage, (page - 1)
								* sizePage);

		return Pair.of(transactions, transactionsCount);
	}
}
